package ru.spb.nicetu.altx.mnemo.node;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import ru.spb.nicetu.altx.mnemo.cell.INodeCell;
import ru.spb.nicetu.altx.mnemo.cell.SettingNodeCell;
import ru.spb.nicetu.altx.mnemo.cell.INodeCell.Categories;
import ru.spb.nicetu.altx.mnemo.extpoint.model.funcs.ISimpleFunc;
import ru.spb.nicetu.altx.mnemo.extpoint.valuetypes.INullCheckable;
import ru.spb.nicetu.altx.mnemo.extpoint.valuetypes.ITemplateValueType;
import ru.spb.nicetu.altx.mnemo.math.managers.IDObjectsManager;
import ru.spb.nicetu.altx.mnemo.math.managers.NodeManager;
import ru.spb.nicetu.altx.mnemo.messages.IRunListener;
import ru.spb.nicetu.altx.mnemo.messages.RunMessage;

// @SuppressWarnings("serial")
public class BaseNode implements INode {

    /**
	 *
	 */
    private static final long serialVersionUID = -8097995900637356567L;

    public BaseNode(String name) {
        super();
        this.name = name;
        this.ID = IDObjectsManager.generateID();
    }

    private long ID;
    private Collection<IRunListener> listeners = new HashSet<IRunListener>();

    /**
     * Список ВХодных ячеек NODE-а
     */
    private List<INodeCell> inputNodeCells = new ArrayList<INodeCell>();
    /**
     * Список ВЫХодных ячеек NODE-а
     */
    private List<INodeCell> outputNodeCells = new ArrayList<INodeCell>();

    /**
     * Список ячеек NODE-а, которые являются настроечными параметрами
     */
    private List<INodeCell> settingNodeCells = new ArrayList<INodeCell>();

    /**
     * Карта ссылок. Хранит для ячеек NODE-а (ВХодных) ссылки на
     * источники-значений - ячейки (ВЫХодные) других NODE-ов.
     */
    protected Map<INodeCell, ICellLink> cellLinks =
            new HashMap<INodeCell, ICellLink>();

    private String concreteName;
    private String name;

    private boolean goodValueSourceCondition;

    // Флаг, показывающий был ли выполнен нод
    protected boolean fWasRunned;

    protected boolean fInterrupted;

    transient protected ISimpleFunc simpleFunc;

    public void addNodeCell(INodeCell cell) {
        if (cell == null)
            return;

        if (cell.getCategorie() == Categories.INPUT) {
            inputNodeCells.add(cell);
        } else if (cell.getCategorie() == Categories.OUTPUT) {
            outputNodeCells.add(cell);
        } else if (cell.getCategorie() == Categories.SETTING) {
            settingNodeCells.add(cell);
        }
    }

    public void removeInputNodeCell(INodeCell cell) {
        inputNodeCells.remove(cell);
        cellLinks.remove(cell);
    }

    public void removeOutputNodeCell(INodeCell cell) {
        outputNodeCells.remove(cell);
        cellLinks.remove(cell);
    }

    public void removeSettingNodeCell(INodeCell cell) {
        settingNodeCells.remove(cell);
    }

    public List<INodeCell> getAllInputCells() {
        return inputNodeCells;
    }

    public List<INodeCell> getAllOutputCells() {
        return outputNodeCells;
    }

    public List<INodeCell> getAllSettingCells() {
        return settingNodeCells;
    }

    public String getName() {
        return name;
    }

    public String getConcreteName() {
        return concreteName;
    }

    public void setConcreteName(String concreteName) {
        this.concreteName = concreteName;
    }

    public void linkMeToNode(INodeCell myCell, ICellLink link) {
        ICellLink prevLink = cellLinks.get(myCell);
        // Если источник был задан ранее, то удаляем ссылку на себя
        if (prevLink != null) {
            INode srcNode = prevLink.getNode();
            srcNode.doUnlinkToDestinationCell(this, myCell, prevLink);
        }
        cellLinks.put(myCell, link);
        if (link != null) {
            INode srcNode = link.getNode();
            srcNode.doLinkToDestinationCell(this, myCell, link);
        }
    }

    public ICellLink getLink(INodeCell myCell) {
        return cellLinks.get(myCell);
    }

    public void removeLinksToNode(INode node) {
        // проходим по все входным ячейкам
        for (INodeCell inputCell : this.getAllInputCells()) {
            // Узнаем, является ли ячейка node-а источником данных для нашей ячейки
            ICellLink link = this.getLink(inputCell);
            if (link == null) {
                continue;
            }
            INode srcNode = link.getNode();
            if (srcNode.equals(node)) {
                // Удаляем ссылку на свою входную ячейку с источника значений
                srcNode.doUnlinkToDestinationCell(this, inputCell, link);

                this.cellLinks.remove(inputCell);
            }
        }
    }

    public void doLinkToDestinationCell(INode node, INodeCell cell,
            ICellLink link) {
    }

    public void doUnlinkToDestinationCell(INode node, INodeCell cell,
            ICellLink link) {
    }

    public long getID() {
        return this.ID;
    }

    public void setID(long newID) {
        this.ID = newID;
    }

    @SuppressWarnings("unchecked")
    public Object getAdapter(Class adapter) {
        return null;
    }

    // FIXME Сериализация SimpleFuncObj
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();

//        System.out.println("Serialize: " + this);
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException,
            ClassNotFoundException {
        in.defaultReadObject();
        NodeManager.getInstance().addNode(this);

//        System.out.println("Deserialize: " + this + "... " + fWasRunned);
    }

    @SuppressWarnings("unchecked")
    public void calcGoodValueSourceCondition() {
        setGoodValueSourceCondition(true);
        for (INodeCell inputCell : this.getAllInputCells()) {
            ICellLink link = this.getLink(inputCell);
            if (link == null) {
                setGoodValueSourceCondition(false);
                return;
            }
        }
        for (INodeCell cell : this.getAllSettingCells()) {
            if (!(cell instanceof SettingNodeCell)) {
                continue;
            }
            SettingNodeCell settingCell = (SettingNodeCell) cell;
            ITemplateValueType value = settingCell.getValue();
            if (value == null) {
                setGoodValueSourceCondition(false);
                return;
            }
            if (value instanceof INullCheckable) {
                boolean isNull = ((INullCheckable) value).isNull();
                if (isNull) {
                    setGoodValueSourceCondition(false);
                    return;
                }
            }
        }
    }

    public boolean isGoodValueSourceCondition() {
        return this.goodValueSourceCondition;
    }

    protected void setGoodValueSourceCondition(boolean goodValueSourceCondition) {
        this.goodValueSourceCondition = goodValueSourceCondition;
    }

    public boolean wasRunned() {
        return fWasRunned;
    }

    public void setWasRunned(boolean wasRunned) {
        this.fWasRunned = wasRunned;
        if (!fWasRunned) {
            fInterrupted = false;
        }
    }

    public boolean isReadyToRun() {
        for (INodeCell cell : getAllInputCells()) {
            ICellLink link = getLink(cell);
            if (link == null)
                return false;

            INode node = link.getNode();
            if (node == null)
                return false;

            ICellLink link2me = new CellLink(this, cell);
            if (!node.hasDataForNodeRun(link2me))
                return false;
            /*
             * if (! node.wasRunned() ) return false;
             */
        }
        return true;
    }

    public boolean isInterrupted() {
        return fInterrupted;
    }

    public void run() {
    }

    @SuppressWarnings("unchecked")
    public ITemplateValueType getCellValue(INodeCell cell) {
        if (simpleFunc == null)
            return null;
        return simpleFunc.getCellValue(cell.getCellID());
    }

    @SuppressWarnings("unchecked")
    public void setCellValue(INodeCell cell, ITemplateValueType value) {
        return;
    }

    public boolean hasDataForNodeRun(ICellLink link) {
        return wasRunned() && !isInterrupted();
    }

    public INodeCell getFirstCell() {
        INodeCell cell = null;
        List<INodeCell> cells = getAllInputCells();
        if (!cells.isEmpty()) {
            cell = cells.get(0);
        } else {
            cells = getAllOutputCells();
            if (!cells.isEmpty()) {
                cell = cells.get(0);
            }
        }
        return cell;
    }

    public void addRunListener(IRunListener listener) {
        listeners.add(listener);
    }

    public void removeRunListener(IRunListener listener) {
        listeners.remove(listener);
    }

    protected void notifyAllRunListeners(RunMessage msg) {
        for (IRunListener listener : listeners) {
            msg.setNode(this);
            listener.handleRunMessage(msg);
        }
    }
} // class BaseNode
